package analizadorlexico;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Hashtable;

public class AnalizadorLexico {

	private boolean fin = false;
	private BufferedReader input;
	private int linea = 1;
	char tmp = ' ';
	
	
	private Hashtable<String, String> tabla_palabras_claves; 			//tabla de palabras claves
	

	public AnalizadorLexico(String nombreArchivo) throws FileNotFoundException
	{
		input = new BufferedReader(new FileReader(nombreArchivo),2048);
		inicializarPalabrasReservadas();
	}
	
	private void inicializarPalabrasReservadas() 
	{		
		//inicializacion de la tabla de palabras claves
		tabla_palabras_claves = new Hashtable<String, String>();
		tabla_palabras_claves.put("if", "if");
		tabla_palabras_claves.put("return", "return");
		tabla_palabras_claves.put("class", "class");
		tabla_palabras_claves.put("new", "new");
		tabla_palabras_claves.put("else", "else");
		tabla_palabras_claves.put("this", "this");
		tabla_palabras_claves.put("extends", "extends");
		tabla_palabras_claves.put("while", "while");
		tabla_palabras_claves.put("boolean", "boolean");
		tabla_palabras_claves.put("char", "char");
		tabla_palabras_claves.put("int", "int");
		tabla_palabras_claves.put("for", "for");
		tabla_palabras_claves.put("void", "void");
		tabla_palabras_claves.put("String", "String");
		tabla_palabras_claves.put("true", "true");
		tabla_palabras_claves.put("false", "false");
		tabla_palabras_claves.put("null", "null");
                tabla_palabras_claves.put("var", "var");
                tabla_palabras_claves.put("dynamic", "dynamic");
                tabla_palabras_claves.put("static", "static");

		
		
	}

	private char leerCaracter() throws IOException{
		int leido=input.read();
		if(leido==-1)       //FIN DE ARCHIVO
			fin=true; 
		else if(leido == '\n') //Nueva linea
		{
			linea++;
		}
		
		return (char)leido;
	}
	
	
	public Token getToken() throws IOException, ErrorLexico
	{
		Token token;
		
		if (fin) //termino el archivo 
		{
			return null; //devuelvo token nulo.
		}
		
		while(tmp==' ') //salteo espacios
		{
			tmp = leerCaracter();
		}
		
		if(Character.isLetter(tmp) || tmp == '_') 
			token = leerIdentificador();
		else if(tmp == '/'){
			token = leerDivisionComentario();
                        if(token == null)
                            return getToken();
                }
		else if(tmp == '=')
			token = leerComparacionAsignacion();
		else if(tmp == '>')
			token = leerMayorIgual();
		else if(tmp == '<')
			token = leerMenorIgual();
		else if(Character.isDigit(tmp))
			token = leerNumero();
		else if(tmp == '\'')
			token = leerChar();
		else if(tmp == '\"')
			token = leerString();
		else if(tmp == '&')
			token = leerConjuncion();
		else if(tmp == '|')
			token = leerDisyuncion();
		else if(tmp == '!')
			token = leerDistintoNot();
		else if((tmp == ';') || (tmp == '(') || (tmp == ')') || (tmp == '{') || (tmp == '}') || (tmp == '.') || 
                        (tmp == ',') || (tmp == '+') || (tmp == '-') || (tmp == '*') || (tmp == '%'))
			token = leerPuntuacion();
		else if (tmp == '\r') //retorno de carro, luego debe venir un caracter de nueva linea.
				{	
					tmp = leerCaracter(); //avanzo un caracter. Consume la nueva linea.
					if ((!fin) && (tmp == '\n'))
					{	
						tmp = leerCaracter(); //me posiciono en el siguiente caracter.
					}
					return getToken();    //llamo recursivamente
				}
		else if ((tmp == '\t') || (tmp == '\n')) //creo que solo para linux, donde la nueva linea es solo un caracter.
				{
					tmp = leerCaracter();
					return getToken();
				}
		else
		{
			if (fin) //termino el archivo
			{
				return null; //devuelvo token nulo.
			}
			throw new ErrorLexico("El caracter "+ tmp + " en la linea " + linea + " no es un caracter valido");
		}
		
		return token;
	}

	private Token leerDistintoNot() throws IOException 
	{
		tmp = leerCaracter();
		String nombre,lexema;
		
		if (tmp == '=') // es el operador !=
		{
			nombre = "distinto";
			lexema = "!=";
		}
		else // es el operador unario not !
		{
			nombre = "not";
			lexema = "!";
			return new Token(nombre,lexema,linea);
		}
		tmp = leerCaracter(); //me posiciono en el siguiente caracter
		return new Token(nombre,lexema,linea);
	}

	private Token leerPuntuacion() throws IOException 
	{
            Token token = null;
		switch (tmp) 
                {
                    case ';': { 
                        token = new Token("punto_y_coma",";",linea);
                        break;
                    }
                    case '(': {
                        token = new Token("parentesis_abre","(",linea);
                        break;
                    }
                    case ')': {
                        token = new Token("parentesis_cierra",")",linea);
                        break;
                    } 
                    case '{': {
                        token = new Token("llave_abre","{",linea);
                        break;
                    }
                    case '}': {
                        token = new Token("llave_cierra","}",linea);
                        break;
                    }                    
                    case '.': {
                        token = new Token("punto",".",linea);
                        break;
                    }
                    case ',': {
                        token = new Token("coma",",",linea);
                        break;
                    }
                    case '+': {
                        token = new Token("mas","+",linea);
                        break;
                    }
                    case '-': {
                        token = new Token("menos","-",linea);
                        break;
                    }
                    case '*':{
                        token = new Token("por","*",linea);
                        break;
                    }
                    case '%': {
                        token = new Token("modulo","%",linea);
                        break;
                    }
		} //fin switch case
		tmp = leerCaracter(); //me posiciono en el siguiente caracter.
		return token;
	}

	private Token leerDisyuncion() throws IOException, ErrorLexico 
	{
		tmp = leerCaracter();
		if (tmp != '|')
		{
			throw new ErrorLexico("Operador mal formado en la linea "  + linea );
		}
		
		tmp = leerCaracter(); //me posiciono en el siguiente caracter
		
		return new Token("disyuncion","||",linea);
	}

	private Token leerConjuncion() throws IOException, ErrorLexico 
	{
		tmp = leerCaracter();
		
		if (tmp != '&')
		{
			throw new ErrorLexico("Operador mal formado en la linea " + linea );
		}
		
		tmp = leerCaracter(); //me posiciono en el siguiente caracter
		
		return new Token("conjuncion","&&",linea);
	}

	private Token leerString() throws IOException, ErrorLexico {
		String lexema = "" + tmp;
		tmp = leerCaracter();
		while(tmp != '"' && !fin && tmp != '\r' && tmp != '\n')
		{
			lexema += tmp;
			tmp = leerCaracter();
			
		}
		lexema += tmp;
		
		if(tmp != '"')
			throw new ErrorLexico("La cadena que comienza en la linea " + linea + " esta mal formada.");
		
		tmp = leerCaracter(); //me posiciono en el siguiente caracter.
		
		return new Token("cadena",lexema,linea);
	}

	private Token leerChar() throws IOException, ErrorLexico {
		String lexema = "" + tmp;
		tmp = leerCaracter();		
		if(tmp == '\'') // '' no es valido.
                    throw new ErrorLexico("Caracter mal formado en la linea " + linea + ".");
		if(tmp == '\\'){
                    tmp = leerCaracter();
		}
                lexema += tmp;
		tmp = leerCaracter();
		lexema += tmp;
		if(tmp != '\'')
                    throw new ErrorLexico("Se esperaba cierre \' en la linea "  + linea + ".");
		
		tmp = leerCaracter(); //me posiciono en el siguiente caracter
		return new Token("caracter",lexema,linea);
	}

	private Token leerNumero() throws IOException, ErrorLexico { 
            String lexema = "" ;         
            while(Character.isDigit(tmp))
            {
                lexema += tmp;
                tmp = leerCaracter();//me posiciono en el siguiente caracter al salir del while
            }
            if (caracterInvalidoParaNumero(tmp))                
                throw new ErrorLexico("Numero mal formado en la linea " + linea + ".");                  

            return new Token("numero",lexema,linea);
            
	}

	private boolean caracterInvalidoParaNumero(char tmp) 
	{
		// es caracter invalido para un numero, si es una letra o no es ninguno de los separadores 
		// listados a continuacion
		return Character.isLetter(tmp) || (tmp != ')' && tmp != ';' && tmp != '!' && tmp != '='
			&& tmp != '<' && tmp != '>' && tmp != '/' && tmp != '+' && tmp != '-' && tmp != '*'
			&& tmp != '%' && tmp != '&' && tmp != '|' && tmp != ',' && tmp != ' ' && tmp != '\r'
			&& tmp != '\n' && tmp != '\t'  && !fin);
	}

	private Token leerMenorIgual() throws IOException {
            
		tmp = leerCaracter();
		String nombre,lexema;
                
		if(!fin && tmp == '=')
		{
			nombre = "menor_o_igual";
			lexema = "<=";
			tmp = leerCaracter();
		}
		else
		{
			nombre = "menor";
			lexema = "<";
		}
		return new Token(nombre,lexema,linea);
	}

	private Token leerMayorIgual() throws IOException {
		tmp = leerCaracter();
                String nombre,lexema;
                
                if(!fin && tmp == '=')
		{
			nombre = "mayor_o_igual";
			lexema = ">=";
			tmp = leerCaracter();
		}
		else
		{
			nombre = "mayor";
			lexema = ">";
		}
		return new Token(nombre,lexema,linea);
	}

	private Token leerComparacionAsignacion() throws IOException {
            tmp = leerCaracter();
            String nombre,lexema;

            if(!fin && tmp == '='){
                nombre = "igual";
                lexema = "==";
                tmp = leerCaracter();
            }
            else {
                nombre = "asignacion";
                lexema = "=";
            }
            return new Token(nombre,lexema,linea);
	}

	private Token leerDivisionComentario() throws IOException, ErrorLexico {
		int principioLinea = linea;
		Token token = null;   
		
		tmp = leerCaracter();
		
		if(tmp == '/') 
		{
                    tmp = leerCaracter();
                    
                        if(tmp == '*'){ //comentario multilinea
                            char ant = leerCaracter(); //caracter anterior al actual
                            boolean fin = false; //determina el final del comentario 
			
                            while(!fin && !this.fin)
                            {
                                tmp = leerCaracter();
                                if(ant == '*' && tmp == '/' ){
                                    tmp = leerCaracter();
                                    if(tmp == '/') //fin del comentario
                                        fin = true;
                                }
                                else
                                        ant = tmp; //actualizo el caracter anterior.
                            }
                            if(!fin)
                                throw new ErrorLexico("Comentario sin cerrar en la linea " + principioLinea);

                            tmp= leerCaracter(); //me posiciono en el siguiente caracter.
                            
                            //nombre = "comentario multilinea";
                            //lexema = "/* */"; 
                        }
                        else{   //comentario simple
                            while(tmp != '\n' && !fin)
				tmp = leerCaracter();
			
                            //nombre = "COMENTARIO SIMPLE";
                            //lexema = "//";
                        }  
		}
		else
		{			
                        token = new Token("division","/",linea);
		}
		return token;
	}

	private Token leerIdentificador() throws ErrorLexico, IOException
	{
		String lexema = "" + tmp;
		String nombre = "identificador";
		tmp = leerCaracter();
		while(Character.isLetter(tmp) || Character.isDigit(tmp) || tmp == '_')
		{
			lexema += tmp;
			tmp = leerCaracter();
		}
		if (caracterInvalidoParaIdentificador(tmp))                
                    throw new ErrorLexico("Identificador mal formado en la linea " + linea + ".");     
                
		String palabra_reservada = tabla_palabras_claves.get(lexema);
                
                if (palabra_reservada != null) //Es una palabra reservada
                {
                        //Emitir token de palabra reservada
                        nombre = palabra_reservada;
                }
                
		return new Token(nombre,lexema,linea);
	}

    private boolean caracterInvalidoParaIdentificador(char tmp) {
     
        return !Character.isLetter(tmp) && tmp != '_' && !Character.isDigit(tmp)&&
               (tmp != ')' && tmp != '(' && tmp != '{' && tmp != '}' && tmp != ';' && tmp != '!' && 
                tmp != '='&& tmp != ' ' && tmp != '<' && tmp != '>' && tmp != '/' && tmp != '+' && 
                tmp != '-' && tmp != '*'&& tmp != '%' && tmp != '&' && tmp != '|' && tmp != ',' &&
                tmp != ' ' && tmp != '\r'&& tmp != '\n' && tmp != '\t' && tmp != '.' && !fin);
    }
			
	
} 
